package cen5501c.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Random;

import cen5501c.common.CmdBase;
import cen5501c.common.CmdParser;
import cen5501c.common.DelayCmd;
import cen5501c.common.ErrorCmd;
import cen5501c.common.FileContentCmd;
import cen5501c.common.FileListCmd;
import cen5501c.common.GetDelayCmd;
import cen5501c.common.GetFileCmd;
import cen5501c.common.ListFilesCmd;
import cen5501c.common.P2PException;
import cen5501c.common.DelayCmd.DelayStatistics;
import cen5501c.common.DelayCmd.DelayStatus;

public class TransferManager {
	private String serverAdd = null;
	private int serverPort = 0;

	public TransferManager(String serverAdd, int serverPort) {
		this.serverAdd = serverAdd;
		this.serverPort = serverPort;
	}

	public DelayStatistics getDelay() throws P2PException {
		long transmission_delay = 0;
		long rtt = 0;
		long processing_time = 0;
		int test_times = 4;
		Random rnd = new Random();
		for (int i = 0; i < test_times; i++) {
			int code = rnd.nextInt(10000);
			GetDelayCmd gdc = new GetDelayCmd();
			gdc.setCode(code);
			DelayStatistics ds = ((DelayCmd) callServer(gdc))
					.getDelayStatistics();
//			System.out.println("#" + i + " time: " + ds.toString());
			transmission_delay += ds.getTransmissionDelay() / test_times;
			rtt += ds.getRtt() / test_times;
			processing_time += ds.getProcessingTime() / test_times;
		}
		DelayStatistics ds = new DelayStatistics(processing_time,
				processing_time, processing_time);
		return ds;
	}

	public CmdBase callServer(CmdBase cb) throws P2PException {
		Socket soc = new Socket();
		String resp = null;
		SocketAddress server_addr = new InetSocketAddress(serverAdd, serverPort);
		long connect_start_time, connected_time, send_start_time, send_end_time, send_end_milli, receive_start_time = 0, receive_end_time = 0, received_first_byte = 0;
		try {
			connect_start_time = System.currentTimeMillis();
			soc.connect(server_addr);
			connected_time = System.currentTimeMillis();
			PrintWriter out = new PrintWriter(soc.getOutputStream(), true);
			InputStream in = soc.getInputStream();
			String string_to_send = CmdParser.cmdToString(cb);
			System.out.println(string_to_send);
			send_start_time = System.nanoTime();
			out.print(string_to_send);
			out.flush();
			send_end_time = System.nanoTime();
			send_end_milli = System.currentTimeMillis();
			StringBuilder sb = new StringBuilder();
			int temp;
			try {
				receive_start_time = System.currentTimeMillis();
				in.skip(56);
				//read the first line, to see if it's a header
				StringBuilder sb1=new StringBuilder();
				int temp_;
				while((temp_=in.read())!='\n'){
					if(received_first_byte==0)
						received_first_byte = System.currentTimeMillis();
					sb1.append((char)temp_);
				}
				String header=sb1.toString()+'\n';
				sb.append(header);
					if(header.indexOf("<FILE-CONTENT")!=-1){
//						System.out.println(header);
						int endPos2=header.lastIndexOf(",");
						int endPos1=header.lastIndexOf(",",endPos2-1)+1;
//						System.out.println("substring: "+header.substring(endPos1,endPos2));
						int fileEnd=Integer.parseInt(header.substring(endPos1,endPos2));
						int startPos2=endPos1-1;
						int startPos1=header.lastIndexOf(",",startPos2-1)+1;
//						System.out.println("substring: "+header.substring(startPos1,startPos2));
						int fileStart=Integer.parseInt(header.substring(startPos1,startPos2));
						int bytesExpected=fileEnd-fileStart;
						int counter=0;
						while ((temp = in.read()) != -1) {
							sb.append((char) temp);
							//if(++counter==bytesExpected)break;
						}
					}
					else if(header.indexOf("<DELAY")!=-1){
						// do nothing here, since delay message contains only one line, and it's already been received
					}
					else{
						//give 5 seconds timeout for waiting the file list
						soc.setSoTimeout(5000);
						while ((temp = in.read()) != -1) {
							sb.append((char) temp);
						}
					}						
			} catch (SocketTimeoutException ste) {
//				ste.printStackTrace();
			} finally {
				receive_end_time = System.currentTimeMillis();
			}
			resp =sb.toString();
//			System.out.println(resp);
//			System.out.println("It took "
//					+ (connected_time - connect_start_time)
//					+ " milliseconds to connect to the server");
//			System.out.println("It took " + (send_end_time - send_start_time)
//					+ " nanoseconds to send");
//			System.out.println("It took "
//					+ (receive_end_time - receive_start_time)
//					+ " milliseconds to receive");

		} catch (SocketTimeoutException ste) {
//			ste.printStackTrace();
			return null;
		} catch (IOException ie) {
//			ie.printStackTrace();
			return null;
		} finally {
			try {
				soc.close();
			} catch (IOException ie) {
//				ie.printStackTrace();
			}
		}

		if (resp == null || resp.equals("")) {
			throw new P2PException("No response received from server.");
		}

		// Successfully got the response String from server, now parse it.
		CmdBase res = null;
		try {
			res = CmdParser.cmdFromString(resp);
		} catch (Exception e) {
//			e.printStackTrace();
		}

		if (res instanceof DelayCmd) {
			DelayCmd dc = (DelayCmd) res;
			if (dc.getDelayStatus() == DelayStatus.ERROR) {
				throw new P2PException("Delay status error, change code");
			}
			long transmission_delay = send_end_time - send_start_time;
			long rtt = connected_time - connect_start_time;
			long processing_time = (received_first_byte - send_end_milli) - rtt;
			DelayStatistics ds = new DelayStatistics(transmission_delay, rtt,
					processing_time);
			dc.setDelayStatistics(ds);
			System.out.println(resp);
		} else if(res instanceof FileListCmd) {
			System.out.println(resp);
		} else if(res instanceof FileContentCmd) {
			System.out.println(resp.substring(0, resp.indexOf(">")));
			System.out.println("File content not printed out");
		}

		if (res instanceof ErrorCmd) {
			throw new P2PException((ErrorCmd) res);
		}
		return res;
	}

	// GET DELAY
	public static void main(String args[]) {
		TransferManager tm = new TransferManager("sand.cise.ufl.edu", 6060);
		try {
			tm.callServer(new ListFilesCmd(new ArrayList<String>()));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
